<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>听力王 Video ABplayer | AB视频复读机</title>
  <link href="static/xgplayer/index.min.css" rel="stylesheet">
	<script src="static/AB_tracker.js"></script>

<!--
https://v2.h5player.bytedance.com/gettingStarted/
<link href="https://cdn.jsdelivr.net/npm/xgplayer@3.0.19/dist/xgplayer.min.css" rel="stylesheet">

v0.2.0 基本可用; 支持使用 ?url=xx.mp4 传递参数; 按钮控制可用;
v0.2.1 上传文件: 视频开始只有音频，没有画面; 注释掉 player.ready事件后正常
v0.2.2 放小视频示例
v0.2.3 上下箭头，去掉默认行为
v0.2.4 add AB_tracker.js
v0.2.5 update tracker
v0.2.6 暂停需要两次 //todo

# 版本号需要修改2个地方：本记录，还有页面顶部

bug:
- 上下箭头和音频高低有冲突，不好解决。先单击视频下区域，再键盘控制上下键可转为控制播放速度。
- 有时候页面时间更新异常，可能和C键功能相关，不确定原因

-->
  <style>
    .control-panel {
      display: flex;
      margin: 10px 0;
    }
    .marker {
      height: 20px;
      background: #ff4757;
      position: absolute;
      top: -5px;
      opacity: 0.7;
    }
	.speed-display {
      position: absolute;
      top: 10px;
      right: 10px;
      background: rgba(0,0,0,0.7);
      color: white;
      padding: 5px 10px;
      border-radius: 4px;
      font-family: Arial;
    }



html,body,h2,#topTitle,p{padding:0px; margin:0;}
#topTitle{text-align:center;}
#topTitle h2{display:block;padding:5px; background:#000; color:orange;}
#topTitle a{color:white; text-decoration:none;}
#topTitle a:hover{text-decoration:underline;}
#topTitle div{font-weight:bold;}
pre{
	border:1px dashed #000;
	padding:5px;
	background:rgb(44,0,30);color:white;
}
pre a{color: orange;}

.control,
.status{padding:10px; margin:5px 10px; border:1px solid #eee; background:#fdfdfd;}
.control{

}

.grey{color:#ddd;}

/*ctrl buttons*/
button{cursor: pointer;}
button.btn{color:#000; padding: 10px 10px; margin-top:20px; display:block; font-size: 25px;}
button.btn:hover {
    background-color: black;
	color:white;
	cursor:pointer;
}



fieldset {
    border: 1px solid #E7E8EA;
    background: #fff;
}

#control,
#help{display:none;}
#showHelp2{opacity:0}

.version{font-size:smaller;}
.red{color:red;}

/*links*/
a {
    color:#0593d3; /*#366799;*/
    text-decoration: none;
}
a:hover {text-decoration: underline;}

#showList{display:none;}

#footer{text-align:center; color:#ddd; padding-top:50px;}

#files{visibility:hidden; position:absolute;}

#path{
	word-wrap: break-word!important;
	white-space: pre-wrap;
}

.hide{
	display: none;
}
  </style>

</head>

<body>
<div id='topTitle'>
	<h2><a target=_blank href="/en" title="返回首页">Video ABplayer</a><span class=version>
v0.2.5
</span></h2>
	<div class="hide">支持AB复读 听写神器</div>
</div>



  <div id="player-container"></div>

  <div class="control-panel">
	<div class="speed-display">播放速度: 1x</div>
  </div>







<div class="status">
	<p><span id="time">CurrentTime: 0.00/Total time(0.00%)</span> | <span id="speed">Speed: 1.00</span></p>
	<div id="div1" class=grey>Status: player status will be shown here.</div>
	<div id="path" class=grey>Path: video Path</div>

	<!-- 上传mp4文件 -->
	<div class="grey">
		Upload video:
		<input type="file" id="files" name="files[]" />
		<button id='uploadFile'>Upload File</button>
		<span id="VideoFileName"></span>
	</div>

</div>






<div class="control">
	<button id='showHelp'>Show Help</button>
	<button id='showButtons'>Show Buttons</button>
	<br /> 
	<span class=grey>Shortcuts: 
		<b>F12</b>[open Console]
		<b>A</b>[set pointA]; 
		<b>B</b>[set pointB]; 
		<b>C</b>[continue]; 
		<b>R</b>[repeat]; 
		<b>Space / Enter</b>[stop/continue];
		<br>

		<b>↑</b>[Increase Speed];
		<b>↓</b>[Decrease Speed];
	</span>
	<!-- 用于转移焦点 focus -->
	<button id='showHelp2'>doNothing</button>
</div>


<fieldset class=control id="control">
	<legend>Shortcuts[Description]</legend>

	<button class="btn" data='A'><b>A</b>[set pointA]</button>
	<button class="btn" data="B"><b>B</b>[set pointB]</button>
	<button class="btn" data="C"><b>C</b>[continue]</button>
	<button class="btn"><b>R</b>[repeat]</button>
	<button class="btn"><b>Space / Enter</b>[stop/continue]</button>

	<button class="btn" data="I"><b>↑</b>[Increase Speed]</button>
	<button class="btn" data="D"><b>↓</b>[Decrease Speed]</button>



	<!-- 用于转移焦点 focus -->
	<button id='showHelp2'>doNothing</button>
</fieldset>




<pre id=help>
<h1>听力王 Video ABplayer  视频AB复读机播放器使用说明书</h1>

1.键盘操作：
空格/Enter 键: 开始和停止播放；
A - 设置A点
B - 设置B点，并开始AB之间循环播放
C - 继续播放
R - 进入AB之间循环播放

↑ - 增大播放速度
↓ - 降低播放速度



<hr>
2.上传文件

方法1: 支持从url传入web视频
格式: ?url=xx.mp3
示例: http://ielts.dawneve.cc/listening/VideoABplayer.html?url=http://j3.biomooc.com:8000/file/video/S01E01.mp4

方法2：支持上传本地电脑视频
点击页面的按钮"Upload File"，选择一个mp4文件，点击确定。


<hr>
Powered by <a target="_blank" href="https://github.com/bytedance/xgplayer">xgplayer</a>

Contact me: QQ 314649593

--the end --
</pre>



<div id=footer>
	<p>
		&copy;2017 -
		2025
		<a target="_blank" href="/en">英语读报</a> 荣誉出品

	</p>
	<p><a target="_blank" href="https://mp.weixin.qq.com/s?__biz=MzI0NDcxNzc5Mg==&mid=2247485239&idx=2&sn=8846604b1ccbcafd8180246002f9b3e4">怎么快速提高英语听力水平?精听</a></p>
	<p><a target="_blank" href="https://mp.weixin.qq.com/s/N8OS79oVpFSibW0MnukJdw">怎么提高英语口语?如何纠音?回音法</a></p>
	<p> 不疯魔，不成活！其实又有哪件事不是如此呢？</p>
</div>




</body>
</html>


<script src="static/xgplayer/index.js" type="text/javascript"></script>

<script>
//<script src="//unpkg.byted-static.com/xgplayer/2.31.2/browser/index.js" type="text/javascript">

//#################################
//tools 
//#################################
// 四舍五入保留小数
function digital(num, n = 2) {
	var num = parseFloat(num) || 0;
    return num.toFixed(n);
}

// 选择元素
function $(s) {
    return document.getElementById(s);
}

// 添加事件监听
function addEvent(obj, ev, fn) {
    if (obj.addEventListener) {
        obj.addEventListener(ev, fn, false);
    } else {
        obj.attachEvent('on' + ev, fn);
    }
}

//获取url中的参数 https://www.cnblogs.com/vicky-li/p/9145026.html
function getUrlParam(name) {
	var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); //构造一个含有目标参数的正则表达式对象
	var r = window.location.search.substr(1).match(reg);  //匹配目标参数
	//if (r != null) return unescape(r[2]); return null; //返回参数值，会导致很多链接在url中打不开
	if (r != null) return (r[2]); return null; //ok
}




//#################################
// player settings
//#################################

var Mstatus = {
    'on': -1,
    'showHelp': -1,
    'ready': 0,
    'repeats': 0,
    'showButtons': -1,
    'pointA': 0,
    'pointB': 0,
	'duration': 0,
};

// 设置为你的本地视频路径
var videoUrl = 'static/Silicon_Valley_cutout.mp4';

//如果有url提供视频，则优先替换
videoUrl= getUrlParam('url') || videoUrl;


//控制台和web显示状态
function show(text){
	$('div1').innerHTML='Status: '+text;
	return '';
	console.log(Mstatus)
}

//快进
function forward(n){
	var n=n||2
	var prevpage="#forward";
	//location = prevpage;
	show('前进'+n+'s')
	//player.seek(player.currentTime + n)
	player.currentTime=player.currentTime + n
}

//快退
function backward(n){
	var n=n||2
	var nextpage="#backward";
	//location = nextpage;
	show('后退'+n+'s')
	//player.seek(player.currentTime - n) //精确到秒
	player.currentTime=player.currentTime - n //更精确
}


// 初始化播放器
/*API: XGPlayer
https://h5player.bytedance.com/api/api.html#play
https://v2.h5player.bytedance.com/config/#%E5%B0%81%E9%9D%A2%E5%9B%BE

其他示例
https://agrahn.gitlab.io/ABLoopPlayer/
*/


// 创建 xgplayer 实例
var player = new Player({
        id: 'player-container',
        url: videoUrl,
        //autoplay: false,
        loop: false,
        //volume: 0.5,
        //mutex: true,
        //danmaku: false,
        //hotkey: true,
        //logo: '',
        //preload: 'auto',
		videoInit: true, //显示视频首帧
		//poster: '//abc.com/**/*.png', //和首帧互斥

        httpConfig: {
            withCredentials: false,
        },

        cors: true,       // 处理跨域资源
        codec: 'avc1.42E01E',  // 明确H.264编码

		width: '100%',
		height: '500px',
		playbackRate: [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3], // 预设速度选项
		defaultPlaybackRate: 1.0,
    });

function initPlayer(videoUrl, isReport=1) {
	//清除定时器
	if('timer0' in Mstatus){
		clearInterval(Mstatus.timer0);
	}

	player.src=videoUrl;

	if(isReport==1){
		// tracker resource path
		setTimeout(function(){
			reportVideoToMonitor(videoUrl, "init");
		}, 100)
	}

	Mstatus['video']=videoUrl;

	// 设置播放器就绪状态
    //player.on('ready', function () {
		// 监视播放器状态
		Mstatus['timer0']=setInterval(function(){
			if(Mstatus.on==-1) return;
			//播放后才有duration
			Mstatus.duration = player.duration;
			var tm=player.currentTime;
			var strTime='CurrentTime: '+digital(tm)+'s/'+ digital(Mstatus.duration)+'s('+ digital(tm/Mstatus.duration*100) +'%)'

			//console.log("-->timer0-status: ", strTime); // status

			if($('time').innerHTML!=strTime){
				$('time').innerHTML=strTime
			}
			//播放速度
			var spd=player.playbackRate;
			if($('speed').innerHTML!="Speed: "+ digital(spd)){
				$('speed').innerHTML="Speed: "+ digital(spd);
			}
			//歌曲
			if($('path').innerHTML!='VideoPath: '+Mstatus['video']){
				$('path').innerHTML='VideoPath: '+Mstatus['video'];
			}

		}, 500); //每0.5s刷新一次状态
        Mstatus.ready = 1;
		show('ready now, press Enter or Space key to play')
    //});

	// 绑定播放结束事件
    player.on('ended', function () {
		Mstatus.on*=-1
		show('finish now, press Enter or Space key to play again')
        //player.currentTime = Mstatus.pointA;
    });
}


// 暂停/继续播放
function togglePause() {
	if(Mstatus.on>0){
		player.pause();
	}else{
		player.play();
	}
	Mstatus.on*=-1;
}

// 开始 AB 复读
function repeatPlay() {
	//清除定时器
	if('timer1' in Mstatus){
		clearInterval(Mstatus.timer1);
	}

	//如果没有定义a点，则a点为开头
	if(!('pointA' in Mstatus)){
		Mstatus['pointA']=0
	}
	//如果没有定义b点，则b点为结尾
	if(!('pointB' in Mstatus)){
		Mstatus['pointB']=Mstatus['duration']-0.01;//如果B点和终点完全相等，则会到终点停止
	}
	
	//如果a比b大呢？交换a和b
	if(Mstatus['pointA']>Mstatus['pointB']){
		//swap(Mstatus['pointA'],Mstatus['pointB'])
		var tmp=Mstatus['pointA'];
		Mstatus['pointA']=Mstatus['pointB'];
		Mstatus['pointB']=tmp;
	}

	//如果A和B之间的距离很小呢？比如完全一样，暂时无解，只能提醒用户按下c键继续了
	if(Math.abs( Mstatus['pointA']-Mstatus['pointB'] )<0.1){
		//暂停
		player.pause();
		Mstatus.on*=-1;
		//高亮提醒
		show("<span class=red>Time between pointA and pointB is too short! Press C to continue.</span>")
		return false;
	}

	//开始播放
	player.play();
	Mstatus.timer1=setInterval(function(){
		var ct=player.currentTime;
		if(ct<Mstatus['pointA'] || ct >=Mstatus['pointB']){
			//player.seek(Mstatus['pointA']);
			player.currentTime = Mstatus['pointA'];
		}
	}, 300)
}


// 更新速度显示: 右上角，右下角
function updatePlaybackRate(increment=0.05){
	const speedDisplay = document.querySelector('.speed-display');
	//右上角
	player.playbackRate+=increment;
	player.playbackRate=Math.round(player.playbackRate*100)/100;
	if(player.playbackRate>3) player.playbackRate=3;
	if(player.playbackRate<0.25) player.playbackRate=0.25;
	//player.playbackRate = newRate;
	const newRate=player.playbackRate;
	speedDisplay.textContent = `播放速度: ${newRate}x`;

	// 添加动画效果
	speedDisplay.style.transform = 'scale(1.2)';
	setTimeout(() => {
		speedDisplay.style.transform = 'scale(1)';
	}, 300);

	//右下角
	var rate2=player.root.querySelector('.xgplayer-playbackrate')
	rate2.getElementsByTagName("p")[0].textContent=`${newRate}x`;
}








// 页面加载完成后初始化
window.onload = function () {
	const speedDisplay = document.querySelector('.speed-display');

    initPlayer(videoUrl);

	//帮助按钮，单击事件
	$('showHelp').onclick=function(){
		Mstatus.showHelp*=-1;
		if(Mstatus.showHelp>0){
			$('help').style.display='block';
			this.innerHTML="Hide Help"
		}else{
			$('help').style.display='none';
			this.innerHTML="Show Help"
		}
		//转移焦点
		$('showHelp2').focus();
	}


	// 上传文件按钮
	$('uploadFile').onclick=function(){
		//console.log("you clicked uploadFile button")
		$('files').click() //触发 文件按钮 点击事件
	}
	// 文件按钮 点击事件
	$('files').onchange=function(evt){
		var files = evt.target.files; // FileList object
		if (files.length==0) return;
		var file = files[0];
		console.log("Filename: ", file.name);
		$("VideoFileName").innerHTML="Filename: " + file.name;

		//上传文件名
		reportVideoToMonitor(file.name, "uploadFile");

		// 转为链接
		if (window.webkitURL) window.URL = window.webkitURL;
		var objectURL = window.URL.createObjectURL(file);
		console.log("File URL: ", objectURL);

		// 传入文件链接给播放器
		//myAudio.src = objectURL;
		//myAudio.play();
		initPlayer(objectURL, 0)
	}



	//显示按钮 按钮，单击事件
	$('showButtons').onclick=function(){
		Mstatus.showButtons*=-1
		if(Mstatus.showButtons>0){
			$('control').style.display='block';
			this.innerHTML="Hide Buttons"
		}else{
			$('control').style.display='none';
			this.innerHTML="Show Buttons"
		}
		//转移焦点
		$('showHelp2').focus()
	}


	//全键盘控制的播放器
	document.onkeydown =function(event){
		//获取键盘事件和数字
		var evt = event ||window.event;
		var key=event.which||event.keyCode;
		console.log(key, evt.key);//打印当前按键码
		
		if(Mstatus.ready==0 && key!=123){
			var alert2='Nothing can be done before loading is completed!'
			console.log(alert2);
			alert(alert2);
			return false;
		}
		
		//前进 后退，左右键
		//if ([33,39].indexOf(key)!=-1){pre();}
		if(key==39){
			if(event.shiftKey==1){
				//shift 则按照2%前进
				forward( Mstatus.duration*0.02 );
			}else{
				forward(2);
			}
		}
		else if(key==37){
			if(event.shiftKey==1){
				//shift 则按照2%后退
				backward( Mstatus.duration*0.02 );
			}else{
				backward(2);
			}
		}
		
		//播放速度，上下键
		else if(key==38){
			if(player.playbackRate<3)
				updatePlaybackRate(0.05);
			else
				console.log("Max rate: 3");
			//取消键盘的默认
			event.preventDefault();
			return false;
		}
		else if(key==40){
			if(player.playbackRate>0.25)
				updatePlaybackRate(-0.05);
			else
				console.log("Min rate: 0.25");
			//取消键盘的默认
			event.preventDefault();
			return false;
		}
		
		//空格32/回车13控制着暂停与否
		else if( [32,13].indexOf(key)!=-1 ){
			togglePause();
			if(key==32){ //阻止空格翻页 默认事件
				event.preventDefault();
				return false;
			}
		}

		//ab复读，a记录a位置
		else if(key==65){
			Mstatus['pointA']=player.currentTime
			show('pointA:'+Mstatus['pointA'])
		}
		//ab复读，b记录b位置
		else if(key==66){
			Mstatus['pointB']=player.currentTime
			show('pointB:'+Mstatus['pointB'])
			//暂停播放
			player.pause();
			//开始从a点到b点反复播放
			show('[b] AB repeat mode now.')
			repeatPlay();
		}
		//ab复读，r开始a到b播放repeat播放
		else if(key==82){
			show('[r] AB repeat mode now.')
			//开始播放
			if( player.paused ){
				player.play();
			}
			repeatPlay();
		}
		//ab复读，按c则continue，不再ab复读
		//if ctrl+c, then do nothing
		else if(key==67 & event.ctrlKey!=true){
			show('[c] continue to play')
			clearInterval(Mstatus.timer1) //清除定时器
			Mstatus['on']=1;
			player.play();
		}

	};


	// 鼠标单击改变速度后，右上角也要改变速度
	player.on('ratechange', () => {
		updatePlaybackRate(0);
	});


	// 按钮被按下，则触发键盘事件
	var keyCode_Dict={
		"A":65,
		"B":66,
		"C":67,
		"R":82,
		//" ":32,
		//"ENTER":13,
		"SPACE / ENTER":13,
		"F12":123,
		"H":72,
		//"E":69,

		"↑": 38,
		// "ArrowUp": 38,
		"↓": 40,
		//"ArrowDown": 40,
	}
	// get button
	var controls=$("control").getElementsByTagName("button")
	for (let i=0; i< (controls.length-1); i++){
		let elem=controls[i];
		let key=String(elem.getElementsByTagName('b')[0].innerHTML).toUpperCase();
		//console.log("bind btn event for: key=",key)
		// get event
		//var event = document.createEvent("KeyboardEvent", {
		let event = new KeyboardEvent("keydown", {
			isTrusted: true,
			altKey: false,
			key: key,
			code: "Key"+key,
			keyCode: keyCode_Dict[key]
		});

		// when click on the button, trigger the key event
		elem.addEventListener('click', function (e) {
		  // e.target matches elem
		  document.dispatchEvent(event);
		}, false);
	}

};

</script>
